﻿using Microsoft.AspNetCore.SignalR;
using SharpAdbClient;
using SimulationCenter.Configs;
using SimulationCenter.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Threading;
using System.Threading.Tasks;

namespace SimulationCenter
{
    public class AdbCenter : IDisposable
    {
        private readonly AdbCenterOptions _options;
        private readonly AdbServer _adbServer = new AdbServer();
        private readonly DeviceMonitor _deviceMonitor =
            new DeviceMonitor(new AdbSocket(new IPEndPoint(IPAddress.Loopback, AdbClient.AdbServerPort)));
        private readonly IHubContext<AdbHub> _adbHub;
        private readonly CancellationTokenSource _cts = new CancellationTokenSource();

        public AdbCenter(AdbCenterOptions options, IHubContext<AdbHub> adbHub)
        {
            _options = options;
            _adbHub = adbHub;

            _deviceMonitor.DeviceConnected += _deviceMonitor_DeviceConnected;
            _deviceMonitor.DeviceDisconnected += _deviceMonitor_DeviceDisconnected;
            _deviceMonitor.DeviceChanged += _deviceMonitor_DeviceChanged;

            PreventMonitorStop();
        }

        public async Task StartServer()
        {
            while (!IsServerRunning())
            {
                _adbServer.StartServer(_options.AdbPath, _options.RestartServerIfNewer);

                await Task.Delay(TimeSpan.FromSeconds(3), _cts.Token);

                if (_cts.Token.IsCancellationRequested) break;
            }
        }

        public async Task EnsureServerRunning()
        {
            if (!IsServerRunning())
                await StartServer();
        }

        public Resp GetStatus()
        {
            return Wrapper(_adbServer.GetStatus);
        }

        public Resp GetDevices()
        {
            return Wrapper(AdbClient.Instance.GetDevices);
        }

        public List<string> GetDeviceSerials()
        {
            return AdbClient.Instance.GetDevices().Select(s => s.Serial).ToList();
        }

        public void Dispose()
        {
            _cts.Cancel();
            _deviceMonitor?.Dispose();
        }

        #region privates

        private bool IsServerRunning()
        {
            return _adbServer.GetStatus().IsRunning;
        }

        private Resp Wrapper<T>(Func<T> function)
        {
            try
            {
                return Resp.Success(function());
            }
            catch (Exception e)
            {
                return Resp.Failure(e);
            }
        }

        private void _deviceMonitor_DeviceDisconnected(object sender, DeviceDataEventArgs e)
        {
            _adbHub.Clients.All.SendAsync("device_disconnected", e.Device);
        }

        private void _deviceMonitor_DeviceConnected(object sender, DeviceDataEventArgs e)
        {
            _adbHub.Clients.All.SendAsync("device_connected", e.Device);
        }

        private void _deviceMonitor_DeviceChanged(object sender, DeviceDataEventArgs e)
        {
            _adbHub.Clients.All.SendAsync("device_changed", e.Device);
        }

        private void PreventMonitorStop()
        {
            Task.Factory.StartNew(() =>
            {
                while (true)
                {
                    if (!_deviceMonitor.IsRunning)
                        _deviceMonitor.Start();

                    if (_cts.Token.IsCancellationRequested)
                        break;

                    Task.Delay(TimeSpan.FromSeconds(3), _cts.Token).Wait(_cts.Token);
                }
            }, _cts.Token);
        }

        #endregion
    }
}
